/**
* Copyright 2015 StreamSets Inc.
*
* Licensed under the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.streamsets.pipeline.stage.origin.http;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.streamsets.pipeline.api.OnRecordError;
import com.streamsets.pipeline.api.Record;
import com.streamsets.pipeline.api.Stage;
import com.streamsets.pipeline.api.StageException;
import com.streamsets.pipeline.config.DataFormat;
import com.streamsets.pipeline.config.JsonMode;
import com.streamsets.pipeline.lib.http.AuthenticationType;
import com.streamsets.pipeline.lib.http.Errors;
import com.streamsets.pipeline.lib.http.HttpMethod;
import com.streamsets.pipeline.lib.http.oauth2.OAuth2ConfigBean;
import com.streamsets.pipeline.lib.http.oauth2.OAuth2GrantTypes;
import com.streamsets.pipeline.lib.http.oauth2.SigningAlgorithms;
import com.streamsets.pipeline.lib.util.ThreadUtil;
import com.streamsets.pipeline.sdk.SourceRunner;
import com.streamsets.pipeline.sdk.StageRunner;
import com.streamsets.pipeline.stage.util.http.HttpStageTestUtil;
import com.streamsets.pipeline.stage.util.http.HttpStageUtil;
import com.streamsets.testing.SingleForkNoReuseTest;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.TestProperties;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URLDecoder;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import static com.streamsets.pipeline.lib.http.oauth2.OAuth2GrantTypes.CLIENT_CREDENTIALS;
import static com.streamsets.pipeline.lib.http.oauth2.OAuth2GrantTypes.RESOURCE_OWNER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Currently tests do not include basic auth because of lack of support in JerseyTest
* so we trust that the Jersey client we use implements auth correctly.
*/
@Category(SingleForkNoReuseTest.class)
public class HttpClientSourceIT extends JerseyTest {
public static final String JWT_BEARER_TOKEN = "urn:ietf:params:oauth:grant-type:jwt-bearer";
static long DELAY = 1100;
private static final String NAMES_JSON = "{\"name\": \"adam\"}\r\n" +
"{\"name\": \"joe\"}\r\n" +
"{\"name\": \"sally\"}";
private static final String EMTPY_RESPONSE = "\n";
public static final String[] EXPECTED_NAMES = {"adam", "joe", "sally"};
private static final int STATUS_TEST_FAIL = 487, STATUS_SLOW_DOWN = 420;
private static long BASELINE_BACKOFF_MS = 100;
private static final long SLOW_STREAM_UNIT_TIME = BASELINE_BACKOFF_MS;
private static final int MAX_NUM_REQUEST_RETRIES = 100;
private static final String CLIENT_ID = "streamsets";
private static final String CLIENT_SECRET = "awesomeness";
private static final String USERNAME = "streamsets";
private static final String PASSWORD = "live long and prosper";
private static final String ALGORITHM = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
private static final String JWT = "{" +
"\"iss\":\"tester@testaccount.com\"," +
"\"scope\":\"https://www.sdc.com/pipelines/awesomeness\"," +
"\"aud\":\"https://www.sdc.com/token\"," +
"\"exp\":1328554385," +
"\"iat\":1328550785" +
"}";
private static String token;
private static int tokenGetCount = 0;
private static KeyPair keyPair;
private static boolean generalStreamResponseSent = false;
private static Response entityOnlyOnFirstRequest(Object entity) {
return onlyOnFirstRequest(Response.ok(entity));
}
private static Response onlyOnFirstRequest(Response.ResponseBuilder responseBuilder) {
if (!generalStreamResponseSent) {
generalStreamResponseSent = true;
return responseBuilder.build();
} else {
return Response.ok().build();
}
}
@Path("/tokenresetstream")
@Produces("application/json")
public static class StreamTokenResetResource {
@GET
public Response getStream(@Context HttpHeaders headers) {
if (token != null) {
String auth = headers.getRequestHeader(HttpHeaders.AUTHORIZATION).get(0);
if (auth == null || !auth.equals("Bearer " + token)) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
if (tokenGetCount == 1) {
// Force the source to get a new token
token = RandomStringUtils.randomAlphanumeric(16);
}
return entityOnlyOnFirstRequest(NAMES_JSON);
}
}
@Path("/stream")
@Produces("application/json")
public static class StreamResource {
@GET
public Response getStream(@Context HttpHeaders headers) {
if (token != null) {
String auth = headers.getRequestHeader(HttpHeaders.AUTHORIZATION).get(0);
if (auth == null || !auth.equals("Bearer " + token)) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
return entityOnlyOnFirstRequest(NAMES_JSON);
}
private static final int NUM_SLOW_DOWN_RESPONSES = 3;
private static int linearReqNum = 0;
private static int expReqNum = 0;
private static long lastReqLinear = 0;
private static long lastReqExp = 0;
private static int slowStreamReqNum = 0;
private static final Response.ResponseBuilder SLOW_STREAM_KEEPALIVE_RESPONSE = Response.ok("\n");
@GET
@Path("/linear-backoff-ok")
public Response getNamesWithLinearBackoff() {
final long acceptableTime = BASELINE_BACKOFF_MS*linearReqNum;
final Response.ResponseBuilder resp = buildBackoffResponseHelper(linearReqNum++, lastReqLinear, acceptableTime);
lastReqLinear = System.currentTimeMillis();
return resp.build();
}
@GET
@Path("/exp-backoff-ok")
public Response getNamesWithExponentialBackoff() {
long acceptableTime = BASELINE_BACKOFF_MS;
for (int i=1; i<expReqNum; i++) {
acceptableTime*=2;
}
final Response.ResponseBuilder resp = buildBackoffResponseHelper(expReqNum++, lastReqExp, acceptableTime);
lastReqExp = System.currentTimeMillis();
return resp.build();
}
public Response.ResponseBuilder buildBackoffResponseHelper(int requestNum, long lastRequestTime, long acceptableTime) {
final long timeSinceLastReq = System.currentTimeMillis() - lastRequestTime;
if (timeSinceLastReq <= acceptableTime) {
Logger.getLogger(HttpClientSourceIT.class).error(String.format(
"Failing backoff test; requestNum %d, lastRequestTime %d, timeSinceLastReq %d",
requestNum,
lastRequestTime,
timeSinceLastReq
));
return Response.status(STATUS_TEST_FAIL);
} else if (requestNum <= NUM_SLOW_DOWN_RESPONSES) {
return Response.status(STATUS_SLOW_DOWN);
} else {
return Response.ok(NAMES_JSON);
}
}
@GET
@Path("/slow-stream")
public Response getNamesWithSlowStream() {
/*
simulate the behavior described by the Twitter streaming API
https://dev.twitter.com/streaming/overview/connecting
1 unit = 100ms
server newline every 1 unit
client times out and reconnects after 3 units
the script will be
1. newline (empty batch)
2. newline (empty batch)
3. newline (empty batch)
4. no response
5. no response
6. no response (timeout: empty batch)
7. newline (empty batch)
8. data (names batch)
*/
Response.ResponseBuilder resp = SLOW_STREAM_KEEPALIVE_RESPONSE;
switch (++slowStreamReqNum) {
case 1:
ThreadUtil.sleep(SLOW_STREAM_UNIT_TIME);
break;
case 2:
ThreadUtil.sleep(SLOW_STREAM_UNIT_TIME);
break;
case 3:
ThreadUtil.sleep(SLOW_STREAM_UNIT_TIME);
break;
case 4:
// make the client time out on this one
ThreadUtil.sleep(SLOW_STREAM_UNIT_TIME * 6);
break;
case 5:
ThreadUtil.sleep(SLOW_STREAM_UNIT_TIME);
break;
case 6:
resp = Response.ok(NAMES_JSON);
break;
default:
resp = Response.status(STATUS_TEST_FAIL);
break;
}
return resp.build();
}
@POST
public Response postStream(String name) {
Map<String, String> map = ImmutableMap.of("adam", "adam", "joe", "joe", "sally", "sally");
String queriedName = map.get(name);
final String entity = "{\"name\": \"" + queriedName + "\"}\r\n";
return entityOnlyOnFirstRequest(entity);
}
}
@Path("/nlstream")
@Produces("application/json")
public static class NewlineStreamResource {
@GET
public Response getStream() {
return entityOnlyOnFirstRequest(
"{\"name\": \"adam\"}\n" +
"{\"name\": \"joe\"}\n" +
"{\"name\": \"sally\"}");
}
}
@Path("/xmlstream")
@Produces("application/xml")
public static class XmlStreamResource {
@GET
public Response getStream() {
return entityOnlyOnFirstRequest(
"<root>" +
"<record>" +
"<name>adam</name>" +
"</record>" +
"<record>" +
"<name>joe</name>" +
"</record>" +
"<record>" +
"<name>sally</name>" +
"</record>" +
"</root>"
);
}
}
@Path("/textstream")
@Produces("application/text")
public static class TextStreamResource {
@GET
public Response getStream() {
return entityOnlyOnFirstRequest(
"adam\r\n" +
"joe\r\n" +
"sally"
);
}
}
@Path("/slowstream")
@Produces("application/text")
public static class SlowTextStreamResource {
@GET
public Response getStream() throws InterruptedException {
Thread.sleep(DELAY);
return entityOnlyOnFirstRequest(
"adam\r\n" +
"joe\r\n" +
"sally"
);
}
}
@Path("/headers")
public static class HeaderRequired {
@GET
public Response getWithHeader(@Context HttpHeaders h) {
// This endpoint will fail if a magic header isnt included
String headerValue = h.getRequestHeaders().getFirst("abcdef");
assertNotNull(headerValue);
return onlyOnFirstRequest(Response.ok(
NAMES_JSON
).header("X-Test-Header", "StreamSets").header("X-List-Header", ImmutableList.of("a", "b")));
}
}
@Path("/preemptive")
public static class PreemptiveAuthResource {
@GET
public Response get(@Context HttpHeaders h) {
// This endpoint will fail if universal is used and expects preemptive auth (basic)
String value = h.getRequestHeaders().getFirst("Authorization");
assertNotNull(value);
return entityOnlyOnFirstRequest(NAMES_JSON);
}
}
@Path("/auth")
@Singleton
public static class AuthResource {
int requestCount = 0;
@GET
public Response get(@Context HttpHeaders h) {
// This endpoint supports the "universal" option which tells the client which auth to use on the 2nd request.
requestCount++;
String value = h.getRequestHeaders().getFirst("Authorization");
if (value == null) {
assertEquals(1, requestCount);
throw new WebApplicationException(
Response.status(401)
.header("WWW-Authenticate", "Basic realm=\"WallyWorld\"")
.build()
);
} else {
assertTrue(requestCount > 1);
}
return entityOnlyOnFirstRequest(NAMES_JSON);
}
}
@Path("/credentialsToken")
@Singleton
public static class Auth2Resource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
public Response post(
@FormParam(OAuth2ConfigBean.GRANT_TYPE_KEY) String type,
@FormParam(OAuth2ConfigBean.CLIENT_ID_KEY) String clientId,
@FormParam(OAuth2ConfigBean.CLIENT_SECRET_KEY) String clientSecret,
@FormParam(OAuth2ConfigBean.RESOURCE_OWNER_KEY) String username,
@FormParam(OAuth2ConfigBean.PASSWORD_KEY) String password
) {
if ((OAuth2ConfigBean.CLIENT_CREDENTIALS_GRANT.equals(type) && CLIENT_ID.equals(clientId) && CLIENT_SECRET.equals(clientSecret)) ||
(OAuth2ConfigBean.RESOURCE_OWNER_GRANT.equals(type) && USERNAME.equals(username) && PASSWORD.equals(password))) {
token = RandomStringUtils.randomAlphanumeric(16);
String tokenResponse = "{\n" +
" \"token_type\": \"Bearer\",\n" +
" \"expires_in\": \"3600\",\n" +
" \"ext_expires_in\": \"0\",\n" +
" \"expires_on\": \"1484788319\",\n" +
" \"not_before\": \"1484784419\",\n" +
" \"access_token\": \"" + token + "\"\n" +
"}";
tokenGetCount++;
return Response.ok().entity(tokenResponse).build();
}
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@Path("/basicToken")
@Singleton
public static class Auth2BasicResource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
public Response post(
@Context HttpHeaders h,
@FormParam(OAuth2ConfigBean.GRANT_TYPE_KEY) String type
) {
String[] creds = new String(
Base64.decodeBase64(h.getHeaderString(HttpHeaders.AUTHORIZATION).substring("Basic ".length())), Charsets.UTF_8)
.split(":");
if (creds.length == 2 && creds[0].equals(CLIENT_ID) && creds[1].equals(CLIENT_SECRET)) {
token = RandomStringUtils.randomAlphanumeric(16);
String tokenResponse = "{\n" +
" \"token_type\": \"Bearer\",\n" +
" \"expires_in\": \"3600\",\n" +
" \"ext_expires_in\": \"0\",\n" +
" \"expires_on\": \"1484788319\",\n" +
" \"not_before\": \"1484784419\",\n" +
" \"access_token\": \"" + token + "\"\n" +
"}";
tokenGetCount++;
return Response.ok().entity(tokenResponse).build();
}
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@Path("/jwtToken")
@Singleton
public static class Auth2JWTResource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
public Response post(
@Context HttpHeaders h,
@FormParam(OAuth2ConfigBean.GRANT_TYPE_KEY) String type,
@FormParam(OAuth2ConfigBean.ASSERTION_KEY) String assertion
) throws Exception {
type = URLDecoder.decode(type, "UTF-8");
if (!type.equals(JWT_BEARER_TOKEN)) {
return Response.status(Response.Status.FORBIDDEN).build();
}
String[] creds = assertion.split("\\.");
Signature sig = Signature.getInstance("SHA256WithRSA");
sig.initSign(keyPair.getPrivate());
sig.update((creds[0] + "." + creds[1]).getBytes());
byte[] signatureBytes = sig.sign();
if (!Arrays.equals(signatureBytes, Base64.decodeBase64(creds[2]))) {
return Response.status(Response.Status.FORBIDDEN).build();
}
String base64dAlg = new String(Base64.decodeBase64(creds[0]));
String base64dJWT = new String(Base64.decodeBase64(creds[1]));
if (base64dAlg.equals(ALGORITHM) &&
base64dJWT.equals(JWT)) {
token = RandomStringUtils.randomAlphanumeric(16);
String tokenResponse = "{\n" +
" \"token_type\": \"Bearer\",\n" +
" \"expires_in\": \"3600\",\n" +
" \"ext_expires_in\": \"0\",\n" +
" \"expires_on\": \"1484788319\",\n" +
" \"not_before\": \"1484784419\",\n" +
" \"access_token\": \"" + token + "\"\n" +
"}";
tokenGetCount++;
return Response.ok().entity(tokenResponse).build();
}
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@Path("/resourceToken")
@Singleton
public static class Auth2ResourceOwnerWithIdResource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
public Response post(
@FormParam(OAuth2ConfigBean.GRANT_TYPE_KEY) String type,
@FormParam(OAuth2ConfigBean.CLIENT_ID_KEY) String clientId,
@FormParam(OAuth2ConfigBean.CLIENT_SECRET_KEY) String clientSecret,
@FormParam(OAuth2ConfigBean.RESOURCE_OWNER_KEY) String username,
@FormParam(OAuth2ConfigBean.PASSWORD_KEY) String password
) {
token = RandomStringUtils.randomAlphanumeric(16);
String tokenResponse = "{\n" +
" \"token_type\": \"Bearer\",\n" +
" \"expires_in\": \"3600\",\n" +
" \"ext_expires_in\": \"0\",\n" +
" \"expires_on\": \"1484788319\",\n" +
" \"not_before\": \"1484784419\",\n" +
" \"access_token\": \"" + token + "\"\n" +
"}";
if ((OAuth2ConfigBean.RESOURCE_OWNER_GRANT.equals(type) &&
USERNAME.equals(username) &&
PASSWORD.equals(password) &&
CLIENT_ID.equals(clientId) &&
CLIENT_SECRET.equals(clientSecret)
)) {
tokenGetCount++;
return Response.ok().entity(tokenResponse).build();
}
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@Path("/unauthorized")
@Singleton
public static class AlwaysUnauthorized {
@GET
public Response get() {
return onlyOnFirstRequest(Response
.status(401)
.header("WWW-Authenticate", "Basic realm=\"WallyWorld\"")
);
}
}
@Override
protected Application configure() {
forceSet(TestProperties.CONTAINER_PORT, "0");
return new ResourceConfig(
Sets.newHashSet(
StreamResource.class,
NewlineStreamResource.class,
TextStreamResource.class,
SlowTextStreamResource.class,
XmlStreamResource.class,
PreemptiveAuthResource.class,
AuthResource.class,
HeaderRequired.class,
AlwaysUnauthorized.class,
HttpStageTestUtil.TestPostCustomType.class,
StreamTokenResetResource.class,
Auth2Resource.class,
Auth2ResourceOwnerWithIdResource.class,
Auth2BasicResource.class,
Auth2JWTResource.class
)
);
}
@Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
@Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.forServlet(
new ServletContainer(
new ResourceConfig(
Sets.newHashSet(
StreamResource.class,
NewlineStreamResource.class,
TextStreamResource.class,
SlowTextStreamResource.class,
XmlStreamResource.class,
PreemptiveAuthResource.class,
AuthResource.class,
HeaderRequired.class,
AlwaysUnauthorized.class,
HttpStageTestUtil.TestPostCustomType.class,
StreamTokenResetResource.class,
Auth2Resource.class,
Auth2ResourceOwnerWithIdResource.class,
Auth2BasicResource.class,
Auth2JWTResource.class
)
)
)
).build();
}
@Before
public void resetServerStatus() {
generalStreamResponseSent = false;
StreamResource.slowStreamReqNum = 0;
StreamResource.linearReqNum = 0;
StreamResource.expReqNum = 0;
StreamResource.lastReqLinear = 0;
StreamResource.lastReqExp = 0;
}
@Test
public void testStreamingHttp() throws Exception {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
}
private void runBatchAndAssertNames(DataFormat dataFormat, HttpClientConfigBean conf) throws StageException {
runBatchAndAssertNames(dataFormat, conf, EXPECTED_NAMES, false);
}
private void runBatchAndAssertNames(DataFormat dataFormat, HttpClientConfigBean conf, boolean delayStream) throws StageException {
runBatchAndAssertNames(dataFormat, conf, EXPECTED_NAMES, delayStream);
}
private void runBatchAndAssertNames(DataFormat dataFormat, HttpClientConfigBean conf, String[] expectedNames, boolean delayStream) throws StageException {
runBatchesAndAssertNames(dataFormat, conf, new String[][] {expectedNames}, delayStream);
}
private void runBatchesAndAssertNames(DataFormat dataFormat, HttpClientConfigBean conf, String[][] expectedNameBatches,
boolean delayStream) throws StageException {
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
try {
for (String[] expectedNames : expectedNameBatches) {
StageRunner.Output output = runner.runProduce(null, 1000);
Map<String, List<Record>> recordMap = output.getRecords();
List<Record> parsedRecords = new ArrayList<>(recordMap.get("lane"));
// Before SDC-4337, this would return nothing
if (delayStream) {
// Before SDC-4337, this would return records 2 and 3, record 1 would be lost
parsedRecords.addAll(getRecords(runner));
}
assertEquals(expectedNames.length, parsedRecords.size());
for (int i = 0; i < parsedRecords.size(); i++) {
if (dataFormat == DataFormat.JSON || dataFormat == DataFormat.XML) {
assertTrue(parsedRecords.get(i).has("/name"));
}
assertEquals(expectedNames[i], extractValueFromRecord(parsedRecords.get(i), dataFormat));
}
}
} finally {
runner.runDestroy();
}
}
@Test
public void testStreamingPost() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.POST;
conf.requestBody = "adam";
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
try {
List<Record> parsedRecords = getRecords(runner);
assertEquals(1, parsedRecords.size());
String[] names = { "adam" };
for (int i = 0; i < parsedRecords.size(); i++) {
assertTrue(parsedRecords.get(i).has("/name"));
assertEquals(names[i], extractValueFromRecord(parsedRecords.get(i), DataFormat.JSON));
}
} finally {
runner.runDestroy();
}
}
@Test
public void testDifferentContentTypesPost() throws Exception {
final Random random = new Random();
String fallbackContentType = "application/default";
for (Map.Entry<String, String> requestEntry : HttpStageTestUtil.CONTENT_TYPE_TO_BODY.entrySet()) {
String expectedContentType = requestEntry.getKey();
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.BATCH;
conf.resourceUrl = getBaseUri() + "test/postCustomType";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 1;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.POST;
conf.requestBody = requestEntry.getValue();
conf.defaultRequestContentType = fallbackContentType;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
if (StringUtils.isBlank(expectedContentType)) {
expectedContentType = fallbackContentType;
} else {
String contentTypeHeader = HttpStageUtil.CONTENT_TYPE_HEADER;
String header = HttpStageTestUtil.randomizeCapitalization(random, contentTypeHeader);
conf.headers.put(header.toString(), expectedContentType);
}
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
try {
List<Record> parsedRecords = getRecords(runner);
assertEquals(1, parsedRecords.size());
final Record record = parsedRecords.get(0);
assertTrue(record.has("/Content-Type"));
assertEquals(expectedContentType, record.get("/Content-Type").getValueAsString());
assertTrue(record.has("/Content"));
assertEquals(requestEntry.getValue(), record.get("/Content").getValueAsString());
} finally {
runner.runDestroy();
}
}
}
@Test
public void testStreamingHttpWithNewlineOnly() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "nlstream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(DataFormat.JSON, conf);
}
@Test
public void testStreamingHttpWithXml() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "xmlstream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.XML;
conf.dataFormatConfig.xmlRecordElement = "record";
runBatchAndAssertNames(DataFormat.XML, conf);
}
@Test
public void testStreamingHttpWithText() throws Exception {
doTestStreamingHttpWithText("textstream", 1000, false);
}
@Test // Tests SDC-4337
public void testSlowStreamingHttpWithText() throws Exception {
doTestStreamingHttpWithText("slowstream", 1000, true);
}
private void doTestStreamingHttpWithText(String endpoint, int timeout, boolean delayStream) throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + endpoint;
// Needs to be higher else grizzly with throw timeout exception, but waitTimeExpired will still return true,
// since that checks maxWaitTime parameter.
conf.client.readTimeoutMillis = 1200;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = timeout;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.TEXT;
runBatchAndAssertNames(DataFormat.TEXT, conf, delayStream);
}
@Ignore // SDC-5504
@Test
public void testHttpWithLinearBackoff() throws Exception {
for (final HttpClientMode mode : HttpClientMode.values()) {
// this should work for all modes
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = mode;
conf.resourceUrl = getBaseUri() + "stream/linear-backoff-ok";
conf.client.readTimeoutMillis = 0;
conf.basic.maxBatchSize = 3;
conf.basic.maxWaitTime = 10000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
final HttpStatusResponseActionConfigBean linearBackoff = new HttpStatusResponseActionConfigBean(
STATUS_SLOW_DOWN,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.RETRY_LINEAR_BACKOFF
);
final HttpStatusResponseActionConfigBean failAction = new HttpStatusResponseActionConfigBean(
STATUS_TEST_FAIL,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.STAGE_ERROR
);
conf.responseStatusActionConfigs = new LinkedList<>();
conf.responseStatusActionConfigs.add(linearBackoff);
conf.responseStatusActionConfigs.add(failAction);
runBatchAndAssertNames(DataFormat.JSON, conf);
resetServerStatus();
}
}
@Ignore // SDC-5504
@Test
public void testHttpWithExponentialBackoff() throws Exception {
for (final HttpClientMode mode : HttpClientMode.values()) {
// this should work for all modes
final HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = mode;
conf.resourceUrl = getBaseUri() + "stream/exp-backoff-ok";
conf.client.readTimeoutMillis = 0;
conf.basic.maxBatchSize = 3;
conf.basic.maxWaitTime = 10000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
final HttpStatusResponseActionConfigBean expBackoff = new HttpStatusResponseActionConfigBean(
STATUS_SLOW_DOWN,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.RETRY_EXPONENTIAL_BACKOFF
);
final HttpStatusResponseActionConfigBean failAction = new HttpStatusResponseActionConfigBean(
STATUS_TEST_FAIL,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.STAGE_ERROR
);
conf.responseStatusActionConfigs = new LinkedList<>();
conf.responseStatusActionConfigs.add(expBackoff);
conf.responseStatusActionConfigs.add(failAction);
runBatchAndAssertNames(DataFormat.JSON, conf);
resetServerStatus();
}
}
@Test
public void testGetNamesWithSlowStream() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream/slow-stream";
conf.client.readTimeoutMillis = (int)SLOW_STREAM_UNIT_TIME*3;
conf.basic.maxBatchSize = 3;
conf.basic.maxWaitTime = 10000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
final HttpStatusResponseActionConfigBean expBackoff = new HttpStatusResponseActionConfigBean(
STATUS_SLOW_DOWN,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.RETRY_EXPONENTIAL_BACKOFF
);
final HttpStatusResponseActionConfigBean failAction = new HttpStatusResponseActionConfigBean(
STATUS_TEST_FAIL,
MAX_NUM_REQUEST_RETRIES,
BASELINE_BACKOFF_MS,
ResponseAction.STAGE_ERROR
);
conf.responseStatusActionConfigs = new LinkedList<>();
conf.responseStatusActionConfigs.add(expBackoff);
conf.responseStatusActionConfigs.add(failAction);
conf.responseTimeoutActionConfig = new HttpTimeoutResponseActionConfigBean(0, ResponseAction.RETRY_IMMEDIATELY);
/*
1. newline (empty batch)
2. newline (empty batch)
3. newline (empty batch)
4. no response
5. no response
6. no response (clienet timeout; empty batch should be returned at this point)
7. newline (empty batch)
8. data (names batch)
*/
runBatchesAndAssertNames(
DataFormat.JSON,
conf,
new String[][] {
new String[0],
EXPECTED_NAMES
},
false
);
}
private List<Record> getRecords(SourceRunner runner) throws StageException {
StageRunner.Output output = runner.runProduce(null, 1000);
Map<String, List<Record>> recordMap = output.getRecords();
return recordMap.get("lane");
}
@Test
public void testNoAuthorizeHttpOnSendToError() throws Exception {
HttpClientSource origin = getUnauthorizedClientSource();
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.setOnRecordError(OnRecordError.TO_ERROR)
.build();
runner.runInit();
try {
runner.runProduce(null, 1000);
List<String> errors = runner.getErrors();
assertEquals(1, errors.size());
} finally {
runner.runDestroy();
}
}
@Test
public void testNoAuthorizeHttpOnStopPipeline() throws Exception {
HttpClientSource origin = getUnauthorizedClientSource();
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.setOnRecordError(OnRecordError.STOP_PIPELINE)
.build();
runner.runInit();
boolean exceptionThrown = false;
try {
runner.runProduce(null, 1000);
List<String> errors = runner.getErrors();
assertEquals(1, errors.size());
} catch (StageException ex){
exceptionThrown = true;
assertEquals(ex.getErrorCode(), Errors.HTTP_01);
}
finally {
runner.runDestroy();
}
assertTrue(exceptionThrown);
}
@Test
public void testNoAuthorizeHttpOnDiscard() throws Exception {
HttpClientSource origin = getUnauthorizedClientSource();
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.setOnRecordError(OnRecordError.DISCARD)
.build();
runner.runInit();
try {
runner.runProduce(null, 1000);
List<String> errors = runner.getErrors();
assertEquals(0, errors.size());
} finally {
runner.runDestroy();
}
}
private String extractValueFromRecord(Record r, DataFormat f) {
String v = null;
if (f == DataFormat.JSON) {
v = r.get("/name").getValueAsString();
} else if (f == DataFormat.TEXT) {
v = r.get().getValueAsMap().get("text").getValueAsString();
} else if (f == DataFormat.XML) {
v = r.get().getValueAsMap().get("name").getValueAsList().get(0).getValueAsMap().get("value").getValueAsString();
}
return v;
}
private HttpClientSource getUnauthorizedClientSource() {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "unauthorized";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
conf.client.useProxy = false;
return new HttpClientSource(conf);
}
@Test
public void testUniversalAuth() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.UNIVERSAL;
conf.client.basicAuth.username = "foo";
conf.client.basicAuth.password = "bar";
conf.httpMode = HttpClientMode.POLLING;
conf.resourceUrl = getBaseUri() + "auth";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 10000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
try {
List<Record> parsedRecords = getRecords(runner);
assertEquals(3, parsedRecords.size());
for (int i = 0; i < parsedRecords.size(); i++) {
assertTrue(parsedRecords.get(i).has("/name"));
assertEquals(EXPECTED_NAMES[i], extractValueFromRecord(parsedRecords.get(i), DataFormat.JSON));
}
} finally {
runner.runDestroy();
}
}
@Test
public void testNoWWWAuthenticate() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.BASIC;
conf.client.basicAuth.username = "foo";
conf.client.basicAuth.password = "bar";
conf.httpMode = HttpClientMode.POLLING;
conf.resourceUrl = getBaseUri() + "preemptive";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 10000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(DataFormat.JSON, conf);
}
@Test
public void testStreamingHttpWithHeader() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.STREAMING;
conf.headers.put("abcdef", "ghijkl");
conf.resourceUrl = getBaseUri() + "headers";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
try {
List<Record> parsedRecords = getRecords(runner);
assertEquals(3, parsedRecords.size());
for (int i = 0; i < parsedRecords.size(); i++) {
assertTrue(parsedRecords.get(i).has("/name"));
// Grizzly is from some reason lower-casing the header attribute names. That is however correct as RFC 2616 clearly
// states that header names are case-insensitive.
assertEquals("StreamSets", parsedRecords.get(i).getHeader().getAttribute("x-test-header"));
assertEquals("[a, b]", parsedRecords.get(i).getHeader().getAttribute("x-list-header"));
assertEquals(EXPECTED_NAMES[i], extractValueFromRecord(parsedRecords.get(i), DataFormat.JSON));
}
} finally {
runner.runDestroy();
}
}
@Test
public void testInvalidELs() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.POLLING;
conf.headers.put("abcdef", "${invalid:el()}");
conf.resourceUrl = getBaseUri() + "${invalid:el()}";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.POST;
conf.requestBody = "${invalid:el()}";
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
List<Stage.ConfigIssue> issues = runner.runValidateConfigs();
assertEquals(3, issues.size());
}
@Test
public void testValidELs() throws Exception {
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.httpMode = HttpClientMode.POLLING;
conf.headers.put("abcdef", "${str:trim('abcdef ')}");
conf.resourceUrl = getBaseUri() + "${str:trim('abcdef ')}";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.POST;
conf.requestBody = "${str:trim('abcdef ')}";
conf.dataFormat = DataFormat.JSON;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
List<Stage.ConfigIssue> issues = runner.runValidateConfigs();
assertEquals(0, issues.size());
}
@Test
public void testOAuth2() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = CLIENT_CREDENTIALS;
conf.client.oauth2.clientId = CLIENT_ID;
conf.client.oauth2.clientSecret = CLIENT_SECRET;
conf.client.oauth2.tokenUrl = getBaseUri() + "credentialsToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
assertEquals(1, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2ResourceOwner() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = RESOURCE_OWNER;
conf.client.oauth2.username = USERNAME;
conf.client.oauth2.password = PASSWORD;
conf.client.oauth2.tokenUrl = getBaseUri() + "credentialsToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
assertEquals(1, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2MultipleBatches() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = CLIENT_CREDENTIALS;
conf.client.oauth2.clientId = CLIENT_ID;
conf.client.oauth2.clientSecret = CLIENT_SECRET;
conf.client.oauth2.tokenUrl = getBaseUri() + "credentialsToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "tokenresetstream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
for (int i = 0; i < 3; i++) {
runner.runProduce(null, 1000);
}
assertEquals(2, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2MultipleBatchesResourceOwner() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = RESOURCE_OWNER;
conf.client.oauth2.username = USERNAME;
conf.client.oauth2.password = PASSWORD;
conf.client.oauth2.tokenUrl = getBaseUri() + "credentialsToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "tokenresetstream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
for (int i = 0; i < 3; i++) {
runner.runProduce(null, 1000);
}
assertEquals(2, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2ResourceOwnerWithClientID() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = RESOURCE_OWNER;
conf.client.oauth2.username = USERNAME;
conf.client.oauth2.password = PASSWORD;
conf.client.oauth2.resourceOwnerClientId = CLIENT_ID;
conf.client.oauth2.resourceOwnerClientSecret = CLIENT_SECRET;
conf.client.oauth2.tokenUrl = getBaseUri() + "resourceToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
assertEquals(1, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2MultipleBatchesResourceOwnerClientId() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = RESOURCE_OWNER;
conf.client.oauth2.username = USERNAME;
conf.client.oauth2.password = PASSWORD;
conf.client.oauth2.resourceOwnerClientId = CLIENT_ID;
conf.client.oauth2.resourceOwnerClientSecret = CLIENT_SECRET;
conf.client.oauth2.tokenUrl = getBaseUri() + "resourceToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "tokenresetstream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
HttpClientSource origin = new HttpClientSource(conf);
SourceRunner runner = new SourceRunner.Builder(HttpClientDSource.class, origin)
.addOutputLane("lane")
.build();
runner.runInit();
for (int i = 0; i < 3; i++) {
runner.runProduce(null, 1000);
}
assertEquals(2, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2WithBasicAuth() throws Exception {
tokenGetCount = 0;
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.BASIC;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = CLIENT_CREDENTIALS;
conf.client.basicAuth.password = CLIENT_SECRET;
conf.client.basicAuth.username = CLIENT_ID;
conf.client.oauth2.tokenUrl = getBaseUri() + "basicToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
assertEquals(1, tokenGetCount);
} finally {
token = null;
}
}
@Test
public void testOAuth2Jwt() throws Exception {
tokenGetCount = 0;
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
try {
DataFormat dataFormat = DataFormat.JSON;
HttpClientConfigBean conf = new HttpClientConfigBean();
conf.client.authType = AuthenticationType.NONE;
conf.client.useOAuth2 = true;
conf.client.oauth2.credentialsGrantType = OAuth2GrantTypes.JWT;
conf.client.oauth2.algorithm = SigningAlgorithms.RS256;
conf.client.oauth2.jwtClaims = JWT;
conf.client.oauth2.key = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());
conf.client.oauth2.tokenUrl = getBaseUri() + "jwtToken";
conf.httpMode = HttpClientMode.STREAMING;
conf.resourceUrl = getBaseUri() + "stream";
conf.client.readTimeoutMillis = 1000;
conf.basic.maxBatchSize = 100;
conf.basic.maxWaitTime = 1000;
conf.pollingInterval = 1000;
conf.httpMethod = HttpMethod.GET;
conf.dataFormat = dataFormat;
conf.dataFormatConfig.jsonContent = JsonMode.MULTIPLE_OBJECTS;
runBatchAndAssertNames(dataFormat, conf);
assertEquals(1, tokenGetCount);
} finally {
token = null;
}
}
}